/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.example.frame.starters.idempotent.core;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.frame.starters.idempotent.annotation.Idempotent;

import java.lang.reflect.Method;

/**
 * 该切面类用于拦截标有 @Idempotent 注解的方法，通过策略模式和工厂模式实现多场景下的幂等性控制。其核心逻辑分为三部分：
 *
 * 1、动态选择处理器：根据注解参数创建不同场景（如HTTP请求、MQ消息）的幂等处理器；
 * 2、执行幂等校验：在目标方法执行前校验请求唯一性，避免重复消费；
 * 3、异常与后处理：处理重复请求异常，并确保资源清理。
 */
@Aspect
public final class IdempotentAspect {

    /**
     * 增强方法标记 {@link Idempotent} 注解逻辑
     */
    @Around("@annotation(org.example.frame.starters.idempotent.annotation.Idempotent)")
    public Object idempotentHandler(ProceedingJoinPoint joinPoint) throws Throwable {
        Idempotent idempotent = getIdempotent(joinPoint);
        IdempotentExecuteHandler instance = IdempotentExecuteHandlerFactory.getInstance(idempotent.scene(), idempotent.type());
        Object resultObj;
        try {
            instance.execute(joinPoint, idempotent);  // 执行校验（如Redis锁）
            resultObj = joinPoint.proceed();         // 执行业务方法
            instance.postProcessing();               //释放锁
        } catch (RepeatConsumptionException ex) {
            /**
             * 触发幂等逻辑时可能有两种情况：
             *    * 1. 消息还在处理，但是不确定是否执行成功，那么需要返回错误，方便 RocketMQ 再次通过重试队列投递
             *    * 2. 消息处理成功了，该消息直接返回成功即可
             */
            if (!ex.getError()) {
                return null;
            }
            throw ex;
        } catch (Throwable ex) {
            // 客户端消费存在异常，需要删除幂等标识方便下次 RocketMQ 再次通过重试队列投递
            instance.exceptionProcessing();
            throw ex;
        } finally {
            IdempotentContext.clean();    // 清理线程上下文（如ThreadLocal）
        }
        return resultObj;
    }

    /*
    获取目标方法上的 @Idempotent 注解，为后续幂等校验提供元数据支持
     */
    public static Idempotent getIdempotent(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        //将切入点（JoinPoint）的签名转换为 MethodSignature 类型，以访问方法级元数据
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //通过反射在原始类中查找与签名匹配的方法（需名称和参数类型完全一致）
        Method targetMethod = joinPoint.getTarget().getClass().getDeclaredMethod(methodSignature.getName(), methodSignature.getMethod().getParameterTypes());
        //从目标方法上读取 @Idempotent 注解配置（如场景类型、幂等策略等）
        return targetMethod.getAnnotation(Idempotent.class);
    }
}
